﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text;
using WinForms.Framework.Utility;

namespace WinForms.Framework.Base
{
    /// <summary>
    /// ViewModel の基底クラスです。
    /// </summary>
    [Serializable]
    public abstract class ViewModelBase : INotifyPropertyChanged, IDataErrorInfo
    {
        /// <summary>
        /// 属性を使用しない独自エラーの一覧です。
        /// </summary>
        private readonly Dictionary<string, string> _errors = new Dictionary<string, string>();

        /// <summary>
        /// 複数プロパティを参照する独自検証を実装する場合に override して、それぞれのモデルに実装してください。
        /// 個別プロパティの検証は、それぞれのプロパティの set に実装してください。
        /// </summary>
        protected virtual void CustomValidation() { }

        /// <summary>
        /// 属性を使用しない独自エラーを設定します。実装する場合は必ずクリア処理も実装してください。
        /// </summary>
        /// <param name="propertyName">プロパティ名を設定します。</param>
        /// <param name="errorMessage">エラーメッセージを設定します。</param>
        /// <remarks>
        /// C#5.0(≒VS2012)以降では CallerMemberName 属性を使用して、パラメーターを省略することが可能です。
        /// </remarks>
        protected void SetError(string propertyName, string errorMessage)
        {
            _errors[propertyName] = errorMessage;
        }

        /// <summary>
        /// 属性を使用しない独自エラーをクリアします。
        /// </summary>
        /// <param name="propertyName">プロパティ名を設定します。</param>
        /// <remarks>
        /// C#5.0(≒VS2012)以降では CallerMemberName 属性を使用して、パラメーターを省略することが可能です。
        /// </remarks>
        protected void ClearError(string propertyName)
        {
            _errors.Remove(propertyName);
        }

        /// <summary>
        /// エラーがあるかないかを取得します。
        /// </summary>
        /// <returns>エラーがある場合：true、エラーがない場合：false</returns>
        public bool HasError()
        {
            return !string.IsNullOrEmpty(Error);
        }

        /// <summary>
        /// プロパティ値が変更されたときに発生します。
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// プロパティ値が変更されたときに発生します。
        /// </summary>
        /// <param name="propertyName">プロパティ名を設定します。</param>
        /// <remarks>
        /// C#5.0(≒VS2012)以降では CallerMemberName 属性を使用して、パラメーターを省略することが可能です。
        /// </remarks>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            var h = PropertyChanged;
            if (h == null)
                return;

            h(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// オブジェクトに関する間違いを示すエラー メッセージを取得します。
        /// </summary>
        public virtual string Error
        {
            get
            {
                // 全エラー連結して返す
                var messages = new List<string>();

                // TryValidatePropertyで、プロパティを検証
                var results = new List<ValidationResult>();
                if (!Validator.TryValidateObject(
                    this,
                    new ValidationContext(this, null, null),
                    results,
                    true))
                {
                    messages.AddRange(results.Select(r => r.ErrorMessage));
                }

                // 独自エラーを実行
                CustomValidation();

                // 個別エラーがあれば個別エラーを返す
                if (_errors.Count > 0)
                    messages.AddRange(_errors.Values);

                return string.Join("／", messages);
            }
        }

        /// <summary>
        /// 指定した名前のプロパティに関するエラー メッセージを取得します。
        /// </summary>
        /// <param name="columnName">エラー メッセージを取得する対象のプロパティの名前。</param>
        /// <returns>プロパティに関するエラー メッセージ。既定値は空の文字列 ("") です。</returns>
        public string this[string columnName]
        {
            get
            {
                // TryValidatePropertyで、プロパティを検証
                var results = new List<ValidationResult>();
                if (!Validator.TryValidateProperty(
                    GetType().GetProperty(columnName).GetValue(this, null),
                    new ValidationContext(this, null, null) { MemberName = columnName },
                    results))
                {
                    // エラーがあれば最初のエラーを返す
                    return results.First().ErrorMessage;
                }

                // 個別エラーがあれば個別エラーを返す
                if (_errors.ContainsKey(columnName))
                    return _errors[columnName];

                return null;
            }
        }

        /// <summary>
        /// 現在のオブジェクトを表す文字列を返します。
        /// </summary>
        /// <returns type="string">プロパティの名称および値を XML 形式に変換した文字列を返します。</returns>
        public override string ToString()
        {
            var xml = new StringBuilder();

            Type type = GetType();
            foreach (PropertyInfo property in type.GetProperties())
            {
                string name = property.Name;
                if (name == "Error" || name == "Item")
                    continue;

                object value = property.GetValue(this, null);

                var collection = value as ICollection;
                if (collection != null)
                {
                    // コレクションの場合は値をフォーマットしてログに書き込みます。
                    xml.Append(Formater.CollectionToXml(name, collection));
                }
                else
                {
                    // コレクション以外の場合は値をそのままログに書き込みます。
                    xml.Append(string.Format("<{0}>{1}</{0}>", name, value));
                }
            }

            return xml.ToString();
        }
    }
}
